Skip to content

feat(badges): accept URL-safe base64 (base64url) badge artifacts - #576

Merged
smarcet merged 4 commits into
mainfrom
feat/base64url-badge-artifact
Aug 4, 2026
Merged

feat(badges): accept URL-safe base64 (base64url) badge artifacts#576
smarcet merged 4 commits into
mainfrom
feat/base64url-badge-artifact

Conversation

@smarcet

@smarcet smarcet commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

ref: https://app.clickup.com/t/9014802374/86bb7wfgw

What

Widen App\Utils\Base64 to also accept the URL-safe base64 alphabet (RFC 4648 §5: - for +, _ for /), so badge QR artifacts can travel as a URL path segment with no percent-encoding tricks.

Why

GET /summits/{id}/badge/{artifact}/validate takes the artifact as a path segment, and Laravel rawurldecodes the path before route matching (Illuminate/Routing/Matching/UriValidator.php) with {badge} compiled to [^/]+ — so any artifact whose base64 wrapper contains / (near-certain for encrypted badges) can never match the route and 404s, no matter how the client percent-encodes it. Full analysis on ClickUp 86bb7wfgw.

Accepting base64url keeps the existing route and payload untouched (one route, one payload — D26): the path segment stays in [A-Za-z0-9_-] plus = padding, all legal in a path segment.

How

  • looksLikeBase64: alphabet regex also accepts - and _
  • tryBase64Decode: normalize (strtr('-_', '+/')) before the strict base64_decode, which rejects the URL-safe alphabet — widening the regex alone is not enough

Additive and backwards-compatible: standard base64 decodes byte-identical; the sole caller is SummitAttendeeBadge::decodeQRCodeFor, so badge-scans POST and validateBadge gain it uniformly and existing clients (scanbadgeapp, sponsor lead flows) are unaffected. Checkin is out of scope either way: AttendeeService::doCheckIn parses the raw QR directly and never unwrapped base64. Badge QR emission does not change — the inner {hex-IV}{base64(ciphertext)} layer decoded by AES::decrypt stays standard; only the per-request transport wrapper may now be url-safe.

Tests

tests/Base64Test.php (new, pure unit — no app boot): standard alphabet regression, url-safe acceptance, spelling equivalence, padding tolerance, garbage rejection. OK (5 tests, 11 assertions).

Deployment

Deploy this before attendee-networking-api's matching change (its resolve_badge re-wraps artifacts url-safe and depends on this decode).

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Added support for decoding URL-safe Base64 values using - and _.
    • Added automatic padding for valid Base64 values without trailing padding.
  • Bug Fixes

    • Improved Base64 validation and decoding consistency while preserving standard Base64 support.
    • Invalid, empty, and malformedly padded inputs are now rejected consistently.
    • Equivalent standard and URL-safe encodings decode to the same data.

Badge QR artifacts travel base64-encoded as a URL path segment
(GET /summits/{id}/badge/{artifact}/validate). The standard alphabet
includes '/', and Laravel rawurldecodes the path before route matching
(UriValidator) with {badge} compiled to [^/]+ - so any artifact whose
wrapper contains '/' can never match the route and 404s with the HTML
error page (ClickUp 86bb7wfgw). Accepting the RFC 4648 section 5
URL-safe alphabet lets callers keep the artifact in the path with no
percent-encoding tricks.

- looksLikeBase64: widen the alphabet to also accept '-' and '_'
- tryBase64Decode: normalize (strtr '-_' -> '+/') before the strict
  base64_decode, which rejects the URL-safe alphabet

Additive and backwards-compatible: standard base64 decodes byte
identical; the sole caller is SummitAttendeeBadge::decodeQRCodeFor, so
badge-scans POST, checkin and validateBadge gain it uniformly and
existing clients are unaffected.

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@smarcet, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d2aea7d1-7a13-42f4-84fa-80284598553d

📥 Commits

Reviewing files that changed from the base of the PR and between 105fab9 and 339a6de.

📒 Files selected for processing (1)
  • app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitApiController.php
📝 Walkthrough

Walkthrough

The Base64 utility now accepts standard and URL-safe alphabets. It validates padding, adds omitted padding, and normalizes URL-safe characters before strict decoding. PHPUnit tests cover valid and invalid inputs.

Changes

URL-safe Base64 support

Layer / File(s) Summary
URL-safe decoding support
app/Utils/Base64.php
looksLikeBase64 accepts URL-safe characters and validates input length and padding. tryBase64Decode normalizes characters and performs strict decoding.
Decoding behavior coverage
tests/Base64Test.php
Tests cover standard and URL-safe input, equivalent decoded bytes, unpadded input, and invalid or malformed padding rejection.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: romanetar

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: support for URL-safe Base64 badge artifacts.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/base64url-badge-artifact

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

📘 OpenAPI / Swagger preview

➡️ https://OpenStackweb.github.io/summit-api/openapi/pr-576/

This page is automatically updated on each push to this PR.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/Utils/Base64.php`:
- Around line 20-22: Update the Base64 validation near the existing preg_match
and length check to reject padding-only and partially padded inputs such as “==”
and “A=”. Validate the unpadded data length and allow either no padding or
exactly the RFC 4648 padding required to reach a multiple of four, while
preserving standard and URL-safe alphabets. Add regression tests covering
padding-only and partially padded inputs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: badd35d8-20a2-4b60-ab4f-e1d44e4ab976

📥 Commits

Reviewing files that changed from the base of the PR and between 70ba47e and e07d794.

📒 Files selected for processing (2)
  • app/Utils/Base64.php
  • tests/Base64Test.php

Comment thread app/Utils/Base64.php Outdated
Per CodeRabbit review on PR #576: the sniff accepted padding-only and
partially padded inputs ('==', 'A=', 'QUFB==') and silently repaired
under-padded ones ('QQ=' decoded as 'QQ=='). Padding may now be omitted
entirely (padBase64 adds it) or must be exactly what the unpadded data
length requires (RFC 4648); the strict decode already rejected these one
step later, so caller-visible behavior only changes for the silently
repaired case, which is now rejected.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

📘 OpenAPI / Swagger preview

➡️ https://OpenStackweb.github.io/summit-api/openapi/pr-576/

This page is automatically updated on each push to this PR.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the shared App\Utils\Base64 helper to accept URL-safe base64 (base64url) so badge QR artifacts can be transported safely as URL path segments without route-matching failures caused by / after Laravel’s raw URL decoding.

Changes:

  • Expand base64 validation to accept - and _ (base64url alphabet) and enforce stricter, RFC-consistent padding rules.
  • Normalize base64url strings (-_/+/) before strict decoding.
  • Add focused unit tests covering standard/base64url equivalence, padding omission tolerance, and invalid input rejection.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
app/Utils/Base64.php Accept base64url characters and normalize before strict decode; tighten padding validation rules.
tests/Base64Test.php Add new unit tests validating standard vs URL-safe decoding, padding behavior, and invalid input rejection.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/Base64Test.php
Comment thread app/Utils/Base64.php
Per Copilot review on PR #576: base64_decode('', true) succeeds with ''
so tryBase64Decode('') returned '' instead of null, and under-padded
input ('QQ=') was still silently repaired at the decode level even
though looksLikeBase64 now rejects it. Short-circuit tryBase64Decode
with looksLikeBase64 so the pair holds one invariant: whatever the
sniff rejects, the decode returns null for. Behavior-neutral for the
sole caller (decodeQRCodeFor only decodes after the sniff passes).

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

📘 OpenAPI / Swagger preview

➡️ https://OpenStackweb.github.io/summit-api/openapi/pr-576/

This page is automatically updated on each push to this PR.

…e param

The swagger published from these annotations described the artifact as
standard base64 only; since this branch the transport wrapper may also
use the RFC 4648 URL-safe alphabet.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

📘 OpenAPI / Swagger preview

➡️ https://OpenStackweb.github.io/summit-api/openapi/pr-576/

This page is automatically updated on each push to this PR.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (1)

app/Utils/Base64.php:28

  • The padding check currently treats an unpadded length that’s already a multiple of 4 as requiring 4 padding chars (because 4 - 0), which is incorrect per RFC 4648. It doesn’t manifest today due to the {0,2} regex cap, but it makes the logic misleading and fragile if the regex ever changes.
        return $paddingLength === 0 || $paddingLength === (4 - $remainder);

@romanetar romanetar left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@smarcet
smarcet merged commit afbd63a into main Aug 4, 2026
21 checks passed
smarcet added a commit that referenced this pull request Aug 4, 2026
* feat(badges): accept URL-safe base64 (base64url) badge artifacts

Badge QR artifacts travel base64-encoded as a URL path segment
(GET /summits/{id}/badge/{artifact}/validate). The standard alphabet
includes '/', and Laravel rawurldecodes the path before route matching
(UriValidator) with {badge} compiled to [^/]+ - so any artifact whose
wrapper contains '/' can never match the route and 404s with the HTML
error page (ClickUp 86bb7wfgw). Accepting the RFC 4648 section 5
URL-safe alphabet lets callers keep the artifact in the path with no
percent-encoding tricks.

- looksLikeBase64: widen the alphabet to also accept '-' and '_'
- tryBase64Decode: normalize (strtr '-_' -> '+/') before the strict
  base64_decode, which rejects the URL-safe alphabet

Additive and backwards-compatible: standard base64 decodes byte
identical; the sole caller is SummitAttendeeBadge::decodeQRCodeFor, so
badge-scans POST, checkin and validateBadge gain it uniformly and
existing clients are unaffected.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(badges): reject malformed base64 padding in looksLikeBase64

Per CodeRabbit review on PR #576: the sniff accepted padding-only and
partially padded inputs ('==', 'A=', 'QUFB==') and silently repaired
under-padded ones ('QQ=' decoded as 'QQ=='). Padding may now be omitted
entirely (padBase64 adds it) or must be exactly what the unpadded data
length requires (RFC 4648); the strict decode already rejected these one
step later, so caller-visible behavior only changes for the silently
repaired case, which is now rejected.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(badges): make tryBase64Decode agree with looksLikeBase64

Per Copilot review on PR #576: base64_decode('', true) succeeds with ''
so tryBase64Decode('') returned '' instead of null, and under-padded
input ('QQ=') was still silently repaired at the decode level even
though looksLikeBase64 now rejects it. Short-circuit tryBase64Decode
with looksLikeBase64 so the pair holds one invariant: whatever the
sniff rejects, the decode returns null for. Behavior-neutral for the
sole caller (decodeQRCodeFor only decodes after the sniff passes).

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(badges): note URL-safe base64 accepted on the validateBadge badge param

The swagger published from these annotations described the artifact as
standard base64 only; since this branch the transport wrapper may also
use the RFC 4648 URL-safe alphabet.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants